#pragma once

#include <utils/concepts/Common.h>

#include <cmath>
#include <type_traits>

#include <gcem.hpp>

#if defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER)
    #include <mathimf.h>
#endif

namespace solar::const_expr
{
    template <Number T> constexpr auto sqrt(T number) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::sqrt(number);
        }

        return std::sqrt(number);
    }

    template <Number T1, Number T2>
    constexpr auto pow(const T1 base, const T2 exp_term) -> gcem::common_t<T1, T2>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::pow(base, exp_term);
        }

        return std::pow(base, exp_term);
    }

    template <Number T> constexpr auto factorial(const T x) -> T
    {
        if (std::is_constant_evaluated())
        {
            return gcem::factorial(x);
        }
        if constexpr (std::is_integral_v<T>)
        {
            return gcem::factorial(x);
        }
        else
        {
            return std::tgamma(x + 1);
        }
    }

    template <Number T> constexpr auto exp(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::exp(x);
        }
        return std::exp(x);
    }

    template <Number T> constexpr auto log(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::log(x);
        }
        return std::log(x);
    }

    template <Number T> constexpr auto sin(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::sin(x);
        }
        return std::sin(x);
    }

    template <Number T> constexpr auto cos(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::cos(x);
        }
        return std::cos(x);
    }

    template <Number T> constexpr auto tan(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::tan(x);
        }
        return std::tan(x);
    }

    template <Number T1, Number T2>
    constexpr auto hypot(const T1 x, const T2 y) -> gcem::common_return_t<T1, T2>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::hypot(x, y);
        }
        return std::hypot(x, y);
    }

    template <Number T> constexpr auto invSqrt(const T x) -> gcem::return_t<T>
    {
#if defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER) || defined(__CUDA_ARCH__)
        if (std::is_constant_evaluated())
        {
#endif
            return gcem::inv_sqrt(x);
#if defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER) || defined(__CUDA_ARCH__)
        }
#endif
#if defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER)

        if constexpr (std::is_same_v<T, float>)
        {
            return ::invsqrtf(x);
        }
        if constexpr (std::is_same_v<T, double>)
        {
            return ::invsqrt(x);
        }
        if constexpr (std::is_same_v<T, long double>)
        {
            return ::invsqrtl(x);
        }
        return ::invsqrt(x);
#endif
#if defined(__CUDA_ARCH__)
        if constexpr (std::is_integral_v<T>)
        {
            return ::rsqrtf(x);
        }
        if constexpr (std::is_same_v<T, float>)
        {
            return ::rsqrtf(x);
        }
        if constexpr (std::is_same_v<T, double>)
        {
            return ::rsqrt(x);
        }
        if constexpr (std::is_same_v<T, long double>)
        {
            return ::rsqrt(static_cast<double>(x));
        }
        // return ::rsqrt(x);

#endif
    }

    template <Number T> constexpr auto asin(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::asin(x);
        }
        return std::asin(x);
    }

    template <Number T> constexpr auto acos(const T x) -> gcem::return_t<T>
    {
        if (std::is_constant_evaluated())
        {
            return gcem::acos(x);
        }
        return std::acos(x);
    }
} // namespace solar::const_expr
